#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>

#include <sys/socket.h>
#include <arpa/inet.h>

#include <avahi-client/client.h>
#include <avahi-client/lookup.h>

#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>

#define REQUESTSIZE 5
#define RESPONSESIZE 50

static AvahiSimplePoll *simple_poll = NULL;

int msg_type;
int content = 1;
int n = 5;
int rating;

void send_udp_datagram(const char* ip, int port) {
	int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	char message[REQUESTSIZE];

	if (msg_type == 2) {
		printf("Sending rating: %d\n", rating);
		sprintf(message, "%d:%d", content, rating);
	} else {
		printf("Requesting recommendations.\n");
		sprintf(message, "%d:r", n);
	}

	struct sockaddr_in servaddr;
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	inet_pton(AF_INET, ip, &servaddr.sin_addr);

	sendto(sockfd, message, sizeof(message), 0, (struct sockaddr *) &servaddr,
			sizeof(servaddr));
	close(sockfd);
}

void listen_udp_datagram(int port) {
	int sockfd;
	struct sockaddr_in servaddr;
	char message[RESPONSESIZE];

	bzero(message, RESPONSESIZE);

	sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(port);

	bind(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
	recv(sockfd, message, sizeof(message), 0);
	close(sockfd);

	printf("[DEBUG]: %s\n", message);
}

static void resolve_callback(AvahiServiceResolver *r,
		AVAHI_GCC_UNUSED AvahiIfIndex interface,
		AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event,
		const char *name, const char *type, const char *domain,
		const char *host_name, const AvahiAddress *address, uint16_t port,
		AvahiStringList *txt, AvahiLookupResultFlags flags,
		AVAHI_GCC_UNUSED void* userdata) {

	assert(r);

	switch (event) {
	case AVAHI_RESOLVER_FAILURE:
		printf("Failed to resolve service '%s'\n", name);
		break;
	case AVAHI_RESOLVER_FOUND: {
		char addr[40];
		avahi_address_snprint(addr, sizeof(addr), address);
		printf("%s:%u (%s)\n", host_name, port, addr);

		send_udp_datagram(addr, port);
		listen_udp_datagram(port);
	}
	}

	avahi_service_resolver_free(r);
}

static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
		AvahiProtocol protocol, AvahiBrowserEvent event, const char *name,
		const char *type, const char *domain,
		AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) {

	AvahiClient *c = userdata;
	assert(b);

	switch (event) {
	case AVAHI_BROWSER_FAILURE:
		printf("AVAHI_BROWSER_FAILURE");
		avahi_simple_poll_quit(simple_poll);
		return;
	case AVAHI_BROWSER_NEW:
		if (!(avahi_service_resolver_new(c, interface, protocol, name, type,
				domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c)))
			printf("Failed to resolve service: %s\n", avahi_strerror(
					avahi_client_errno(c)));
		break;
	case AVAHI_BROWSER_REMOVE:
	case AVAHI_BROWSER_ALL_FOR_NOW:
	case AVAHI_BROWSER_CACHE_EXHAUSTED:
		break;
	}
}

static void client_callback(AvahiClient *c, AvahiClientState state,
		AVAHI_GCC_UNUSED void * userdata) {
	assert(c);

	if (state == AVAHI_CLIENT_FAILURE) {
		fprintf(stderr, "Server connection failure: %s\n", avahi_strerror(
				avahi_client_errno(c)));
		avahi_simple_poll_quit(simple_poll);
	}
}

int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
	AvahiClient *client = NULL;
	AvahiServiceBrowser *sb = NULL;
	int error;
	int ret = 1;

	if (argc < 2)
		msg_type = 1;
	else {
		msg_type = 2;
		rating = atoi(argv[1]);
	}

	if (!(simple_poll = avahi_simple_poll_new())) {
		fprintf(stderr, "Failed to create simple poll object.\n");
		goto fail;
	}

	client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0,
			client_callback, NULL, &error);

	if (!client) {
		fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error));
		goto fail;
	}

	if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
			AVAHI_PROTO_UNSPEC, "_recommender._udp", NULL, 0, browse_callback,
			client))) {
		fprintf(stderr, "Failed to create service browser: %s\n",
				avahi_strerror(avahi_client_errno(client)));
		goto fail;
	}

	avahi_simple_poll_loop(simple_poll);
	ret = 0;

	fail:

	if (sb)
		avahi_service_browser_free(sb);

	if (client)
		avahi_client_free(client);

	if (simple_poll)
		avahi_simple_poll_free(simple_poll);

	return ret;
}
